package classification.geneticAlgorithm;

import java.util.Random;

public class GeneticAlgorithm
{
    /* you should guarantee the mutation num is bigger than 1 */
    private final double MUTATION_RATIO  = 0.01;

    /* you should guarantee the crossover pair is a integer */
    private final double CROSSOVER_RATIO = 0.4;
    private final double SELECT_RATIO    = 0.6;
    private final int    POOL_SIZE       = 3;
    private int          DNALength       = 0;
    private int          XMLElemNum      = 0;
    private double       probability[];

    public GeneticAlgorithm(int initDNALength, int initXMLElemNum)
    {
        this.DNALength = initDNALength;
        this.XMLElemNum = initXMLElemNum;
    }

    public void init(Individual[] geneticPool)
    {
        Random rand = new Random();
        for (int indvNum = 0; indvNum < geneticPool.length; indvNum++)
        {
            geneticPool[indvNum] = new Individual(this.DNALength);
            int DNAPos = 0;
            for (int i = 0; i < this.XMLElemNum; i++)
            {
                for (int j = i; j < this.XMLElemNum; j++)
                {
                    if (i == j)
                    {
                        geneticPool[indvNum].DNA[DNAPos] = 1;
                    }
                    else
                    {
                        geneticPool[indvNum].DNA[DNAPos] = rand.nextInt(2);
                    }
                    DNAPos++;
                }
            }
        }
    }

    public Individual[] select(final Individual[] geneticPool)
    {
        Individual[] selectPool = new Individual[(int) (this.POOL_SIZE * this.SELECT_RATIO)];

        /* calculate the sum of fitness value */
        double fitnessSum = 0.0;
        double maxFitness = 0.0;
        double fitness = 0.0;
        Individual bestIndv = null;
        for (int indvNum = 0; indvNum < geneticPool.length; indvNum++)
        {
            fitness = geneticPool[indvNum].value;
            if (fitness > maxFitness)
            {
                maxFitness = fitness;
                bestIndv = geneticPool[indvNum];
            }
            fitnessSum += fitness;
        }

        /* calculate the probability value of each individual */
        this.probability = new double[geneticPool.length];
        probability[0] = geneticPool[0].value / fitnessSum;
        for (int indvNum = 1; indvNum < geneticPool.length; indvNum++)
        {
            probability[indvNum] = probability[indvNum - 1]
                    + geneticPool[indvNum].value / fitnessSum;
        }

        /*
         * select individuals from geneticPool according to the probability and
         * best individual will be reserved as default
         */
        selectPool[0] = new Individual(bestIndv);

        double randNum = 0.0;
        Random rand = new Random();
        int index = 0;
        for (int selectionNum = 1; selectionNum < selectPool.length; selectionNum++)
        {
            randNum = (rand.nextInt(100) + 1) / (double) 100 - 0.0001;
            index = 0;
            while (probability[index++] < randNum)
            {}
            selectPool[selectionNum] = new Individual(geneticPool[index - 1]);
        }
        return selectPool;
    }

    public Individual[] crossover(Individual[] geneticPool)
    {
        Individual[] crossoverPool = new Individual[this.POOL_SIZE
                - (int) (this.POOL_SIZE * this.SELECT_RATIO)];

        Random rand = new Random();
        double randNum = 0.0;
        int index = 0;
        int crossoverPos = 0;

        Individual mom, dad, son, daughter;
        for (int crossoverPair = 0; crossoverPair < crossoverPool.length / 2; crossoverPair++)
        {
            randNum = (rand.nextInt(100) + 1) / (double) 100 - 0.0001;
            index = 0;
            while (probability[index++] < randNum)
            {}
            mom = geneticPool[index - 1];

            randNum = (rand.nextInt(100) + 1) / (double) 100 - 0.0001;
            index = 0;
            while (probability[index++] < randNum)
            {}
            dad = geneticPool[index - 1];

            crossoverPos = rand.nextInt(this.DNALength);
            son = new Individual(this.DNALength);
            daughter = new Individual(this.DNALength);

            /* copy unchanged DNA part */
            for (int start = 0; start < crossoverPos; start++)
            {
                son.DNA[start] = dad.DNA[start];
                daughter.DNA[start] = mom.DNA[start];
            }

            /* copy crossover DNA part */
            for (int pos = crossoverPos; pos < this.DNALength; pos++)
            {
                son.DNA[pos] = mom.DNA[pos];
                daughter.DNA[pos] = dad.DNA[pos];
            }
            crossoverPool[crossoverPair * 2] = son;
            crossoverPool[crossoverPair * 2 + 1] = daughter;
        }
        return crossoverPool;
    }

    public void mutation(Individual[] geneticPool)
    {
        Random rand = new Random();
        int indvNum = 0;
        int mutationPos = 0;
        for (int mutationNum = 0; mutationNum < geneticPool.length
                * this.MUTATION_RATIO; mutationNum++)
        {
            indvNum = rand.nextInt(geneticPool.length);
            mutationPos = rand.nextInt(this.DNALength);

            int oldValue = geneticPool[indvNum].DNA[mutationPos];
            int newValue = (oldValue + 1) % 2;
            geneticPool[indvNum].DNA[mutationPos] = newValue;
        }
    }

    public int getPoolSize()
    {
        return this.POOL_SIZE;
    }
}
